use pest::Parser;   
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "src/lexer.pest"]
pub struct SysYLexer;

#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Const(String, usize),
    Int(String, usize),
    Void(String, usize),
    If(String, usize),
    Else(String, usize),
    While(String, usize),
    Break(String, usize),
    Continue(String, usize),
    Return(String, usize),
    Plus(String, usize),
    Minus(String, usize),
    Mul(String, usize),
    Div(String, usize),
    Mod(String, usize),
    Assign(String, usize),
    Eq(String, usize),
    Neq(String, usize),
    Lt(String, usize),
    Gt(String, usize),
    Le(String, usize),
    Ge(String, usize),
    Not(String, usize),
    And(String, usize),
    Or(String, usize),
    LParen(String, usize),
    RParen(String, usize),
    LBrace(String, usize),
    RBrace(String, usize),
    LBracket(String, usize),
    RBracket(String, usize),
    Comma(String, usize),
    Semicolon(String, usize),
    Identifier(String, usize),
    IntegerConst(i32, usize),
    Error(String, usize),
}

impl Token {
    pub fn format(&self) -> String {
        match self {
            Token::Const(s, l) => format!("CONST {} at Line {}.", s, l),
            Token::Int(s, l) => format!("INT {} at Line {}.", s, l),
            Token::Void(s, l) => format!("VOID {} at Line {}.", s, l),
            Token::If(s, l) => format!("IF {} at Line {}.", s, l),
            Token::Else(s, l) => format!("ELSE {} at Line {}.", s, l),
            Token::While(s, l) => format!("WHILE {} at Line {}.", s, l),
            Token::Break(s, l) => format!("BREAK {} at Line {}.", s, l),
            Token::Continue(s, l) => format!("CONTINUE {} at Line {}.", s, l),
            Token::Return(s, l) => format!("RETURN {} at Line {}.", s, l),
            Token::Plus(s, l) => format!("PLUS {} at Line {}.", s, l),
            Token::Minus(s, l) => format!("MINUS {} at Line {}.", s, l),
            Token::Mul(s, l) => format!("MUL {} at Line {}.", s, l),
            Token::Div(s, l) => format!("DIV {} at Line {}.", s, l),
            Token::Mod(s, l) => format!("MOD {} at Line {}.", s, l),
            Token::Assign(s, l) => format!("ASSIGN {} at Line {}.", s, l),
            Token::Eq(s, l) => format!("EQ {} at Line {}.", s, l),
            Token::Neq(s, l) => format!("NEQ {} at Line {}.", s, l),
            Token::Lt(s, l) => format!("LT {} at Line {}.", s, l),
            Token::Gt(s, l) => format!("GT {} at Line {}.", s, l),
            Token::Le(s, l) => format!("LE {} at Line {}.", s, l),
            Token::Ge(s, l) => format!("GE {} at Line {}.", s, l),
            Token::Not(s, l) => format!("NOT {} at Line {}.", s, l),
            Token::And(s, l) => format!("AND {} at Line {}.", s, l),
            Token::Or(s, l) => format!("OR {} at Line {}.", s, l),
            Token::LParen(s, l) => format!("L_PAREN {} at Line {}.", s, l),
            Token::RParen(s, l) => format!("R_PAREN {} at Line {}.", s, l),
            Token::LBrace(s, l) => format!("L_BRACE {} at Line {}.", s, l),
            Token::RBrace(s, l) => format!("R_BRACE {} at Line {}.", s, l),
            Token::LBracket(s, l) => format!("L_BRACKT {} at Line {}.", s, l),
            Token::RBracket(s, l) => format!("R_BRACKT {} at Line {}.", s, l),
            Token::Comma(s, l) => format!("COMMA {} at Line {}.", s, l),
            Token::Semicolon(s, l) => format!("SEMICOLON {} at Line {}.", s, l),
            Token::Identifier(s, l) => format!("IDENT {} at Line {}.", s, l),
            Token::IntegerConst(s, l) => format!("INTEGER_CONST {} at Line {}.", s, l),
            Token::Error(s, l) => format!("Error type A at Line {}: Mysterious character {}", l, s),
        }
    }
}

fn integer(input: &str) -> i32 {
    // 0x or 0X begin, hex number
    if input.starts_with("0x") || input.starts_with("0X") {
        let mut num = 0;
        for c in input.chars().skip(2) {
            let digit = match c {
                '0'..='9' => c as i32 - '0' as i32,
                'a'..='f' => c as i32 - 'a' as i32 + 10,
                'A'..='F' => c as i32 - 'A' as i32 + 10,
                _ => panic!("Invalid hexadecimal digit: {}", c),
            };
            num = num * 16 + digit;
        }
        num
    } 
    // 0 begin, oct number
    else if input.starts_with("0") {
        let mut num = 0;
        for c in input.chars().skip(1) {
            if !('0'..='7').contains(&c) {
                panic!("Invalid octal digit: {}", c);
            }
            num = num * 8 + (c as i32 - '0' as i32);
        }
        num
    } else {
        // decimal number
        let mut num = 0;
        for c in input.chars() {
            if !c.is_digit(10) {
                panic!("Invalid decimal digit: {}", c);
            }
            num = num * 10 + (c as i32 - '0' as i32);
        }
        num
    }
}



pub fn tokenize(input: &str) -> Vec<Token> {
    let pairs = SysYLexer::parse(Rule::TOKEN, input).unwrap();
    let mut tokens = Vec::new();
    
    for pair in pairs.into_iter().next().unwrap().into_inner() {
        if pair.as_rule() == Rule::EOI {
            break;
        }
        let (line, _) = pair.as_span().start_pos().line_col();
        let token_str = pair.as_str().to_string();
        let token = match pair.as_rule() {
            Rule::CONST => Token::Const(token_str, line),
            Rule::INT => Token::Int(token_str, line),
            Rule::VOID => Token::Void(token_str, line),
            Rule::IF => Token::If(token_str, line),
            Rule::ELSE => Token::Else(token_str, line),
            Rule::WHILE => Token::While(token_str, line),
            Rule::BREAK => Token::Break(token_str, line),
            Rule::CONTINUE => Token::Continue(token_str, line),
            Rule::RETURN => Token::Return(token_str, line),
            Rule::PLUS => Token::Plus(token_str, line),
            Rule::MINUS => Token::Minus(token_str, line),
            Rule::MUL => Token::Mul(token_str, line),
            Rule::DIV => Token::Div(token_str, line),
            Rule::MOD => Token::Mod(token_str, line),
            Rule::ASSIGN => Token::Assign(token_str, line),
            Rule::EQ => Token::Eq(token_str, line),
            Rule::NEQ => Token::Neq(token_str, line),
            Rule::LT => Token::Lt(token_str, line),
            Rule::GT => Token::Gt(token_str, line),
            Rule::LE => Token::Le(token_str, line),
            Rule::GE => Token::Ge(token_str, line),
            Rule::NOT => Token::Not(token_str, line),
            Rule::AND => Token::And(token_str, line),
            Rule::OR => Token::Or(token_str, line),
            Rule::L_PAREN => Token::LParen(token_str, line),
            Rule::R_PAREN => Token::RParen(token_str, line),
            Rule::L_BRACE => Token::LBrace(token_str, line),
            Rule::R_BRACE => Token::RBrace(token_str, line),
            Rule::L_BRACKT => Token::LBracket(token_str, line),
            Rule::R_BRACKT => Token::RBracket(token_str, line),
            Rule::COMMA => Token::Comma(token_str, line),
            Rule::SEMICOLON => Token::Semicolon(token_str, line),
            Rule::IDENT => Token::Identifier(token_str, line),
            Rule::INTEGER_CONST => Token::IntegerConst(integer(&token_str), line),
            Rule::ERROR => Token::Error(token_str, line),
            _ => unreachable!(),
        };
        tokens.push(token);
    }
    
    let error_tokens: Vec<&Token> = tokens.iter().filter(|token| matches!(token, Token::Error(_, _))).collect();
    if !error_tokens.is_empty() {
        for token in error_tokens {
            println!("{}", token.format());
        }
        std::process::exit(0);
    }
    
    tokens
}